fix(gemini): strip JSON-Schema keywords the tool API rejects - #4761
Merged
Conversation
Gemini's function-declaration schema is an OpenAPI 3.0 subset. Zod 4's
`z.toJSONSchema` emits `const` for every `z.literal()` and `$ref`/`$defs`
for every reused schema, so any tool defined in Zod produced a 400
("Unknown name \"const\"") that aborted the whole tool batch.
The sanitizer now:
- rewrites `const` to a single-value `enum` for strings, and to a type
plus a description hint for numbers and booleans (Gemini's `enum` only
takes strings)
- inlines local `$ref`s before dropping `$defs`, degrading cyclic or
unresolvable refs to a permissive object instead of an empty schema
- maps `oneOf` onto `anyOf` and folds `allOf` members into the parent
- collapses a `["string","null"]` type union onto `type` + `nullable`
- drops the remaining draft-2020-12 keywords Gemini has no field for
- recurses schema-aware, so a property named `const` (or any other
keyword) and data under `enum`/`default`/`example` survive intact
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174zpe4326XDbrzA4UANeQR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Gemini 400s the whole tool batch on a JSON-Schema keyword it has no field for:
Gemini's function-declaration schema is an OpenAPI 3.0 subset. Zod 4's
z.toJSONSchema(draft 2020-12, used byzodToJsonSchemainpackages/runtime/src/zod-schema.ts) emitsconstfor everyz.literal()and$ref+$defsfor every reused schema — so any tool defined in Zod hit this. One bad keyword anywhere in the tree aborts every tool in the request, not just the offending one.sanitizeGeminiSchemaalready stripped a handful of keywords (additionalProperties,$ref,patternProperties, …) but notconst, and stripping$refwhile dropping$defsleft an empty schema behind.Changes
packages/runtime/src/providers/gemini-provider.ts:const→ an accepted form. A string literal becomes a single-valueenum; a number or boolean literal becomes itstypeplus aMust be X.description hint, since Gemini'senumtakes strings only.$refinlined against the root document before$defsis dropped. A cyclic or unresolvable ref degrades to{ type: "object" }rather than{}.oneOf→anyOf(same meaning to the model, andanyOfis in Gemini's dialect);allOffolded into the parent, mergingpropertiesand unioningrequired, with parent keys winning.["string","null"]→type: "string"+nullable: true— Gemini'stypeis one string, and the union is Zod's nullable shape.not,if/then/else,prefixItems,contains,uniqueItems,multipleOf,examples,readOnly/writeOnly,deprecated,contentEncoding/contentMediaType,$comment,$defs.const(oradditionalProperties) survives, and data underenum/default/exampleis copied verbatim instead of being walked as a schema.Existing behavior kept: tool-name sanitization, and the
itemsbackfill for array schemas.Tests
Five cases added to
packages/runtime/tests/providers/gemini-provider.test.ts:constrewriting for string/number/boolean literals, a property literally namedconst,$refinlining with$defsremoval, a recursive$ref, and theoneOf/allOf/nullable-union mapping.npx vitest run tests/providers/gemini-provider*.test.ts— 107 passed.npm run lint --workspace=packages/runtime(tsc) clean.Unrelated pre-existing failures in
tests/providers(fal, kie, atlascloud, manifest-models, replicate, together, topaz) are present onmainin this environment and untouched by this change.🤖 Generated with Claude Code
https://claude.ai/code/session_0174zpe4326XDbrzA4UANeQR
Generated by Claude Code